// *********************************************************
//      Copyright (c) Kosta Gaitanis
//      All rights reserved
//
//      k.gaitanis@gmail.com
//
// *********************************************************

#ifndef WORLD_H
#define WORLD_H

#include "Item.h"
#include "Texture.h"
#include "Camera.h"
#include "Light.h"

class PaintItem;

/**
 * \class World
 * \brief The world contains items, textures, lights and a camera.
 * \author Kosta Gaitanis
 */
class World
{
public:
    enum PaintMethod {Recursive,    ///< Paints the parent and then all its children in
                                    ///  the order they were added. Transparency can produce artefacts
                                    ///  with this method, but is much faster and simpler to implement.

                      FarToNear     ///< Paints the items from the fursthest to the nearest compared
                                    ///  to the camera position. This allows for transparency effect to
                                    ///  be rendered correctly.
                     };
    /**
     * Constructor
     * Creates a new empty world object.
     */
    World();

    /**
     * Destructor
     * Deletes all the items, textures, lights and cameras.
     */
    ~World();

    /**
     * @return A const pointer to the world item.
     */
    Item* getWorldItem() const;

    /**
     * Adds a new item to the world.
     * The item should not be deleted from the outside once it has been added to
     * the world.
     *
     * @param item The item to be added.
     */
    void addItem(Item* item);

    /**
     * @brief Tries to find the given item in the child hierarchy of this item.
     * @param item the item we are looking for
     * @param recursive Set to true to also look inside childrend of children
     * @return True if item was found
     */
    bool hasItem(Item* item, bool recursive = false) const;

    /**
     * @brief adds a new light to the world
     * @param light the new light
     */
    void addLight(Light* light);

    /**
     * @brief adds a new texture to the world that can be applied to items
     * @param texture The new texture
     */
    void addTexture(const Texture* texture);

    /**
     * @brief Assigns a texture to an item
     * @param texture The texture to be assigned. If the texture has not yet been
     * added to the world. It will be first added.
     * @param item The item on which the texture will be applied.
     */
    void bindTexture(const Texture* texture, Item* item);

    /**
     * @param texture The texture we are looking for.
     * @return True if the texture has been added to world.
     */
    bool hasTexture(const Texture* texture) const;

    /**
     * @param index The index of the light
     * @return A pointer to the given light. 0 if not found.
     */
    Light* getLight(int index = 0) const;

    /**
     * @return The list of texture that have been added to the world.
     */
    QList<const Texture*> getTextures() const;

    const Texture* getTexture(const QString& name) const;

    /**
     * @return A pointer to the camera of the world.
     */
    Camera* getCamera() const;

    /**
     * Paints the world.
     * this includes camera, lights and items.
     */
    void paint();

    /**
     * Initialiazes all the objects that need GL initialization.
     * This includes, camera, lights, textures and items.
     */
    void initializeGL();   

private:
    Q_DISABLE_COPY(World)

    /**
     * @brief Paints all the items of the world using the specified painting method.
     * @param method The method used to decide in which order the items will be painted
     *      Recursive will paint the items recursively starting from the world and
     *      painting each child item and then all its children recursively.
     *
     *      FarToNear creates a list of all the items sorted by distance to the camera
     *      position. The items are painted in order from the furthest to the nearest
     *      regardless of their hierarchy. For this to happen, the absolute transformation
     *      matrix of each item is calculated (costly) and an approximation of its position
     *      is derived from that matrix. Transparency is rendered correctly using this method.
     */
    void paintItems(PaintMethod method);

    /**
     * @brief paint an item and then call this method on each of this items children
     * @param item the item to be painted (including all its children and childrens children)
     */
    void paintItemRecursively(Item* item);

    /**
     * @brief Returns a list of all the child items of item. The items are wrapped in a PaintItem
     *        class that contains the pointer to the item, the distance of the item from
     *        the camera and the global transformation matrix of the item.
     *
     * @param item The item to start with.
     * @param transform The transformation to be applied to item's original transformation matrix.
     * @return A list of paintItems containing the item and all its children (recursively).
     */
    QList<PaintItem*> getPaintItems(Item* item, Matrix transform = Matrix()) const;

    Item* m_world;                      /// > The world item. All added items are children of this item
    Camera* m_camera;                   /// > The camera.
    QList<Light*> m_lights;             /// > The list of lights.
    QList<const Texture*> m_textures;   /// > The list of textures.

};

/**
 * @class PaintItem
 * @brief The PaintItem class is simply a container for attaching an item to its distance
 *        to the camera and to a transformation matrix.
 */
class PaintItem {
public:
    PaintItem(Item* item, double distance, Matrix transform);

    Item *item;
    double distance;
    Matrix transform;
};

/**
 * @brief The ItemDistanceMoreThan class can compare two PaintItems based on their
 *        distance to the camera. Used to sort a list of PaintItems.
 */
class ItemDistanceMoreThan {
public:
    /**
     * @param {item1, item2} The items to be compared
     * @return true if item1->distance > item2->distance
     */
    bool operator()(PaintItem *item1, PaintItem *item2) const;

};

#endif // WORLD_H
